Перейти к основному содержимому

Цифровая подпись документов в Эстонии с помощью DigiDocService

· 7 мин. чтения

В Эстонии с 2000 года вступил в силу закон о цифровых подписях, которые стали юридически равноценны обычным рукописным. Вскоре была создана и техническая основа - компания SertifitseerimisKeskus (буквально - «центр сертификации») принадлежащая банкам и телекоммуникацонным операторам (а не государству, представляете себе!) и схема обмена данными по X.509 стандарту. Эта статья расчитана в большей мере на программистов.

Цифровая подпись?

Подпись как оказывается очень важна, а признаваемая государством - тем более. Снижаются затраты на распечатку и/или доставку счетов по оплате, договоров между работником и работодателем. Я уже не говорю про обычное подтверждение что документ прислан точно нужным человеком, а не хакером. Спасает положение то что у каждого гражданина Эстоини есть сертификат подписи, но его недостаточно. Проблема в том что одной подписи-закарючки в IT-мире недостаточно. Подпись в расширенном виде на самом деле включает в себя набор данные, в том числе не статичные.

  1. Стороны подписывающие документ

  2. Собственно документ или его отпечаток (говорящий о неизменном состоянии со времени подписания)

  3. Свидетели (нотариус) и роль сторон

  4. Время, место

Контейнер всей этой информации решили сделать на XML и назвать .ddoc расширением и связать с онлайн-сервисом создания и подтверждения подписей — Digidoc. За основу берутся основные свойства эстонской ID-карточки - авторизацияподпись и шифрование и в результате имеем:

  • цифровая подпись файлов (DigiDoc клиент, портал или третья сторона через DigiDocService)
  • шифрование и дешифрование файлов (DigiDoc клиент)
  • подтверждение действительности (digidoccheck)
  • подпись электронной почты
  • подпись или авторизация с помощью мобильного телефона (Mobiil-ID)

Контейнер со времени создания претерпел некоторые изменения, сейчас есть версия 1.3 основана на стандарте XAdES-X-L расширенных электронных подписей.

Процесс создания подписи с DigiDocService

Теперь собственно о главном что может понадобится на любом сайте. Допустим вы продаёте рога и копыта и хотите всё юридически правильно оформить. По-старинке это было бы типичный checkbox мол «согласен с условиями». Теперь же можно получить юридически действительную подпись клиента под любым договором, распиской купли-продажи или договора предоставления услуги.


Что-бы это у себя сделать Sertifitseerimiskeskus предоставляет услугу DigiDocService по SOAP, и для этого опубликованы следующие списки WSDL-методов: http://www.sk.ee/DigiDocService/DigiDocService_2_3.wsdl //почти live https://digidocservice.sk.ee/?wsdl //live - работает с CURL только вместе с Juur-SK.crt https://www.openxades.org:8443/?wsdl //test
В обмене данными участвуют следующие стороны

  • Клиент с нормальной ид-карточкой и софтом

  • Наше серверное приложение

  • Digidoc-узел (см. wsdl выше)

  • OCSP сервер, публикующий устаревшие или отозванные сертификаты id-карт

Если опустить очевидное, то процесс в общем выглядит так

  1. Создание сессии между приложением и digidoc (StartSession) с передачей инфы о контейнере (который может включать несколько подписываемых файлов) — либо целиком файлы, либо их SHA1-хэш. Запоминаем вернувшися SessionCode у себя

  2. Можно запросить с помощью GetSignatureModules сразу готовый html (со всякими апплетами, activex компонентами..) для того что-бы получить сертификаты клиента

  3. Клиент авторизуется передавая данные серверу, который вызывает PrepareSignature (signCertHex, signCertId), получает обратно бинарный хэш контейнера документов SignedInfoDigest который клиент должен подписать

  4. Клиент подписывает SignedInfoDigest введя PIN2 — генерируется подпись signValueHex и передаётся в FinalizeSignature(). На этом моменте digidoc проверяет действительность сертификата пользователя у OSCP

  5. В успешном случае можно уже скачать .ddoc файл. Если оригинальные файлы не отсылались, то их в base64-форме внедряют в вернувшийся xml. Сессия закрывается CloseSession.

Авторизацию и подпись можно поставить и с помощью мобильного телефона, где добавляются ещё и сторона оператора (MSSP), но я этот случай здесь не рассматриваю.

О качестве ddservice

Поставляемый php-пакет в качестве примера полон багов и говнокода — написанный под php4 надо постараться что-бы прикрутить его к PEAR под php5, надо увеличить таймаут curl во всех запросах с 4 секунд на что-либо существенное.

У меня в процессе вылетали ошибкииспользовал test-среду вместо live 200: Failed to get signature confirmation/notary // используйте live среду вместо test Validation constraint violation: data type mismatch xsd:string in element 'Sesscode' //приведите сессию в int-тип

Чтение сертификата с ID-карты

Возвращаемся к двум основным пунктам подписи. До вызова PrepareSignature метода, на странице есть компонент (Applet, ActiveX либо зависимый от браузера plugin) который считывает два параметра

  • signCertHex — сертификат подписи переведённый из цифрового DER формата в HEX

  • signCertId — идентификатор приватного ключа

В качестве компонента DigiDocService возвращает ActiveX для IE (EIDCard.cab файлик) и аплет для остальных ( SignApplet_sig.jar,iaikPkcs11Wrapper_sig.jar ). ActiveX в плане интеграции довольно прост - можно javascriptом вызвать оба метода, следовательно прикрутить их к любому дизайну, другое дело что у меня этот компонент не работает (уж и драйверы переустанавливал - не помогло). А вот аплет мало того что подгружает яву, так и в дизайн не вписывается со своими кнопками.

Firefox - EstEID XPCOM v0.4

В идеале каждый браузер мог бы завести свой plugin который бы поддерживал обработку созданного в RIA

<object type="application/x-esteid" />

Но глядя как это тянется c 2006 года в Opera, очевидно что это будет долго. Впрочем Firefox уже умеет использовать onepin-opensc-pkcs11.dll как с помощью SK'шного хака, так и с помощью плагина EstEID XPCOM v0.4 написанного в RIA и Smartlink. В результате имеем красивую картинку в Firefox и нет нужды читать страшные мануалы по копированию dll-файлов и ошибок «Teie arvutisse on vaja installeerida PKCS#11 ohjurprogramm

Этот компонент успешно может яваскриптом выкачивать данные об id-карте к примеру— document.getElementById('esteid').signCert.cert. Но этот сертификат подписи для DigiDoc надо первести в шестнадцатиричный формат из PEM. Примерно так:
        $sTempKey=str_replace(array(         "-----BEGIN CERTIFICATE-----",         "-----END CERTIFICATE-----",         "\n"         ),'',$sTempKey);         $sTempKey=base64_decode($sTempKey);         $sTempKey=bin2hex($sTempKey);
EstID-плагин теперь переехал и выпускается как open-source

Internet explorer - EIDCard / dsiglite2 + idutil

EIDCard.cab компонент судя по существующему коду прост - создаёшь объект, ссылаешься на этот файл, потом через vbscript спрашиваешь сертификат или запрашиваешь подпись digest'а - всё как в Firefox. Что-бы он заработал нормально надо соблюдать следующие заповеди

  1. Выключить автоматическое удаление сертификатов в ID-card tool если вы под Windows Vista с правами администратора
  2. Держать объект EIDCard за пределами формы, иначе он не будет доступен вовсе
  3. При создании подписи VBscript по умолчанию пытается записать результат в поле signValueHex, вот только видимо кто-то использует такую же переменную в компоненте из-за чего подпись не передаётся серверу.

dsiglite2.cab это альтернатива для создания подписи, правда его найти удалось только у swedbank'а на странице и он слишком низкоуровневый, пришлось отказаться. Третий компонент очень полезен - idutil.cab понимает события вытаскивания карточки, хоть и с использованием JScript'а (в итоге получается каша из трёх ECMAscript диалектов)

<!--Использование idutil для чтения личных данных в IE--> <div id="mTag"></div> <OBJECT  ID="myCard" CLASSID="CLSID:7F9F89F2-F12B-4B25-9C69-7358F38B898B" CodeBase="idutil.cab"></OBJECT> <SCRIPT LANGUAGE="JScript" FOR="myCard" EVENT="CardInserted() ">     var mTag = document.getElementById("mTag");     mTag.innerHTML = "Reading data...";     timerId = window.setInterval("reading()",500); </SCRIPT> <SCRIPT LANGUAGE="JScript" FOR="myCard" EVENT="CardRemoved() ">     var mTag = document.getElementById("mTag");     window.clearInterval(timerId);     mTag.innerHTML = "Card removed"; </SCRIPT> <SCRIPT LANGUAGE="JScript"> var timerId; function reading(){     var mTag = document.getElementById("mTag");     window.clearInterval(timerId);     mTag.innerHTML = "";     try {         myCard.ReadCard();         mTag.innerHTML='Hello,'+myCard.familyName+' '+myCard.personalCode;         }     catch(e) {         mTag.innerHTML="Reading failed";         }     } </SCRIPT>

Если вы «переписываете» ddservice, то имеет смысл сохранить корень (wsdl класс) и переписать обёртку. Обратите внимание что после чтения сертификата и PREPARE, нельзя перенаправлять на другую страницу - надо делать POST на ту же самую страницу, иначе аплет будет ругаться. Вторая проблема - правильно заменять маркеры вида 1 на реальные данные. Ну и третья проблема - расставить пути к jar-файлам если у вас используется ЧПУ, иначе плагины будут искаться в локальной несуществующей папке.

Validity confirmation

Как я выше писал - существует digidoccheck с помощью которого можно проверить подпись. Листочек этот юридической силы не имеет в качестве распечатки цифровой подписи, но иметь на всякий случай не помешает. Разберёмся же какие тут данные есть и откуда они приходят

  • Данные о файле и пользователе - приходят в ответе FinalizeSignature. Там очень просто - ходи по массиву и выдёргивай что тебе надо.

  • Серийный номер сертификата - десятичная версия  находится в ['SignedDocInfo']->SignatureInfo->Signer->Certificate->IssuerSerial и легко переводится в hex

  • Сертифицирующая сторона (собственно SK) в явном виде отсутсвует, надо выдирать из ['SignedDocInfo']->SignatureInfo->Confirmation->ResponderCertificate->Issuer

  • Хеш публичного ключа сертифицирующей стороны. На данный момент это ESTEID-SK 2007 и он есть на сайте, но правильней конечно запрашивать его динамически через WSDL метод GetSignersCertificate(). В php к результату надо приделать "-----BEGIN CERTIFICATE-----" и концовку (что-бы получить PEM формат) и взять от него openssl_x509_parse(). Внутри и будет находится заветная 4806DEBE ... Вариант захардкодить и обновлять каждые X лет я не рассматриваю

  • Хеш OCSP-сертификата о действительности карточки (HASH VALUE OF VALIDITY CONFIRMATION (OCSP RESPONSE)) берётся из WSDL метода getNotary() из которого OcspData попадает в sha1(base64_decode(...))

Ваш покорный слуга на время написания статьи нашёл мега-баг в том листочке что генерирует Digidoc Client - там OCSP responce hash всегда одинаковый. Кроме того я бы добавил хеши самих документов для печати, иначе слишком много зависимости от памяти SK. Ну и в подарок - метод hex2bin

        function hex2bin($data) {             $len = strlen($data);             for($i=0;$i<$len;$i+=2) {             $newdata .= pack("C",hexdec(substr($data,$i,2)));             }             return $newdata;         }